package controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;

import modele.Square;
import modele.World;
import modele.WorldElem.Earth;
import modele.WorldElem.Flooded;
import modele.WorldElem.Grass;
import modele.WorldElem.None;
import modele.WorldElem.Rock;
import modele.WorldElem.Sand;
import modele.WorldElem.SecondaryState;
import modele.WorldElem.Tree;
import modele.WorldElem.WaterSource;

public class WaterFlowRule1 extends Rule{
	private boolean[][] modified;
	
	public WaterFlowRule1(World w) {
		super(w);
	}

	@Override
	public void applyRule(Earth e, Square s) {
		apply(s);
	}

	@Override
	public void applyRule(Grass g, Square s) {
		apply(s);
		if(g.getSecondaryStateValue() == SecondaryState.FLOODED ){
			Flooded f = (Flooded) g.getSecondaryState();
			if(f.getTimeFlooded()> 4){
				Earth e = new Earth();
				e.setSecondaryState(f);
				s.setState(e);
				modified[s.getPosX()][s.getPosY()] = true;
			}
		}
	}

	@Override
	public void applyRule(Rock r, Square s) {
		apply(s);
	}

	@Override
	public void applyRule(Sand sa, Square s) {
		apply(s);
		
	}

	@Override
	public void applyRule(Tree t, Square s) {
		apply(s);
		if(t.getSecondaryStateValue() == SecondaryState.FLOODED ){
			Flooded f = (Flooded) t.getSecondaryState();
			if(f.getTimeFlooded() > 14){
				Earth e = new Earth();
				e.setSecondaryState(f);
				s.setState(e);
				modified[s.getPosX()][s.getPosY()] = true;
			}
		}
		
		
	}
	
	@Override
	public void applyRule(WaterSource ws, Square s) {
		apply(s);
		int waterProduction = 6;
		if(s.getSecondaryStateValue() == SecondaryState.FLOODED){
			Flooded f = (Flooded) s.getSecondaryState();
			f.setWaterLevel(f.getWaterLevel() + waterProduction);
		}
		else{
			Flooded f = new Flooded();
			f.setWaterLevel(waterProduction);
			s.setSecondaryState(f);
		}
		if(ws.getSecondaryStateValue() == SecondaryState.FLOODED ){
			Flooded f = (Flooded) ws.getSecondaryState();
			if(f.getTimeFlooded() > 50){
				Random rand = new Random();
				if(rand.nextFloat() < 0.15){
					Rock r = new Rock();
					r.setSecondaryState(f);
					s.setState(r);
					modified[s.getPosX()][s.getPosY()] = true;
				}
			}
		}
	}
	

	
	/**
	 * @param squares
	 * @param s
	 * @return
	 */
	private ArrayList<Square> getSquareOfSmallerAltitude(ArrayList<ArrayList<Square>> squares, Square s){
		Iterator<ArrayList<Square>> itArray = squares.iterator();
		ArrayList<Square> result = new ArrayList<Square>();
		while(itArray.hasNext()){
			Iterator<Square> itSquare = itArray.next().iterator();
			while(itSquare.hasNext()){
				Square tmp = itSquare.next();
				int deltaAltitude = s.getAltitude() - tmp.getAltitude(); 
				if(deltaAltitude > 1){
					result.add(tmp);
				}
			}
		}
		return result;
	}
	
	
	
	/**
	 * 
	 * @param s la case de la carte buffer que l'on visite
	 */
	private void apply(Square s){
		if(s != getEvolvedSquareMap()[s.getPosX()][s.getPosY()]){
			throw new RuntimeException();
		}
		Square squareWorldBuff =(Square) getWorld().getSquareMap()[s.getPosX()][s.getPosY()].clone();
		ArrayList<ArrayList<Square>> squaresWorld = s.getSquareAround(getWorld(), 1);
		
		ArrayList<Square> smallerSquaresWorld = getSquareOfSmallerAltitude(squaresWorld, s);
		
		ArrayList<Square> bufferSquares = new ArrayList<Square>();
 		
		for(Square square : smallerSquaresWorld){
			bufferSquares.add((Square) square.clone());
		}
		
		
		Random rand = new Random();
		int indice;
		while(!bufferSquares.isEmpty() && squareWorldBuff.getSecondaryStateValue() == SecondaryState.FLOODED){
			
			//on initialise l'indice
			//version aléatoire
//			indice =  rand.nextInt(bufferSquares.size());
			
			//version vers le plus petit
			indice = getMinAltitudeIndex(bufferSquares);
			
			
			//on modifie le niveau de l'eau de squareWorldBuff qui nous assure que si
			//au début de l'application de l'event le niveau était de n on ne peut pas
			//répandre n+1 volume d'eau.
			Flooded fTmpBuf = (Flooded)(squareWorldBuff.getSecondaryState()); 
			fTmpBuf.setWaterLevel(fTmpBuf.getWaterLevel()-1);
			if(fTmpBuf.getWaterLevel() <= 0){
				None n = new None();
				squareWorldBuff.setSecondaryState(n);
			}
			
			//on modifie aussi le niveau de l'eau dans le tableau evolvedSquareMap
			//puisqu'il représente les modification qui seront réellement appliquées.
			Flooded fTmp = (Flooded)(s.getSecondaryState()); 
			fTmp.setWaterLevel(fTmp.getWaterLevel()-1);
			modified[s.getPosX()][s.getPosY()] = true;
			if(fTmp.getWaterLevel() <= 0){
				None n = new None();
				s.setSecondaryState(n);
			}
			
			
			//on modifie le tableau buffer pour s'assurer de la cohérence des boucle
			Square sTmpBuff = bufferSquares.get(indice);
			if(sTmpBuff.getSecondaryStateValue() != SecondaryState.FLOODED){
				fTmp = new Flooded();
				sTmpBuff.setSecondaryState(fTmp);
			}
			else{
				fTmp = (Flooded) sTmpBuff.getSecondaryState();
				fTmp.setWaterLevel(fTmp.getWaterLevel()+1);
			}
		
			
			
			//on met réellement à jour le tableau des evolved map
			Square sTmp = getEvolvedSquareMap()[sTmpBuff.getPosX()][sTmpBuff.getPosY()];
			modified[sTmp.getPosX()][sTmp.getPosY()] = true;
			if(sTmp.getSecondaryStateValue() != SecondaryState.FLOODED){
				fTmp = new Flooded();
				sTmp.setSecondaryState(fTmp);
			}
			else{
				fTmp = (Flooded) sTmp.getSecondaryState();
				fTmp.setWaterLevel(fTmp.getWaterLevel()+1);
			}
			
			
			//on retire de la liste des bufferTmp l'ensemble 
			Iterator<Square> it = bufferSquares.iterator();
			while(it.hasNext()){
				sTmp = it.next();
				if(squareWorldBuff.getAltitude() - sTmp.getAltitude() <= 1){
					it.remove();
				}
			}
		}
		
		//si la case était inondé à n est l'est à n+1 alors on augmente timeFlooded
		if(getWorld().getSquareMap()[s.getPosX()][s.getPosY()].getSecondaryStateValue() == SecondaryState.FLOODED
				&& s.getSecondaryStateValue() == SecondaryState.FLOODED){
			Flooded f = ((Flooded)(s.getSecondaryState()));
			f.setTimeFlooded(f.getTimeFlooded()+1);
			modified[s.getPosX()][s.getPosY()] = true;
		}
	}
	
	public boolean isModify(int i, int j){
		return modified[i][j];
	}
	
	
	@Override
	public void prepareToEvolve() {
		setEvolvedSquareMap(getWorld().cloneSquareMap());
		modified = new boolean[getWorld().getWidth()][getWorld().getWidth()];
		
	}

	@Override
	public int getRuleValue() {
		return WATERFLOW1;
	}

	/**
	 * retourne l'index de la plus petite altitude.
	 *
	 * @param squares ne doit pas être vide
	 * @return
	 */
	public int getMinAltitudeIndex(ArrayList<Square> squares){
		int minIndex = 0;
		int minAltitude = squares.get(minIndex).getAltitude();
		for(int i = 1 ; i < squares.size() ; i++){
			int altitudeTmp =squares.get(i).getAltitude(); 
			if(altitudeTmp < minAltitude){
				minAltitude = altitudeTmp;
				minIndex = i;
			}
		}
		return minIndex;
	}

	
}
